package cn.xej.boot_redis01.aop;

import cn.xej.boot_redis01.mapper.TbLogMapper;
import cn.xej.boot_redis01.pojo.TbLog;
import cn.xej.boot_redis01.util.SqlUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Objects;

/**
 * @author <a href="mailto:enjie.xu@yunhutech.com">xiaoming</a>
 * @since 2021/6/30
 */

@Aspect
@Component
public class ServiceAspect {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    @Autowired
    private TbLogMapper tbLogMapper;

    private TbLog tbLog;

    /**
     * Pointcut定义切点
     * public修饰符的   返回值任意  com.cy.controller包下面的任意类的任意方法任意参数
     */
//    @Pointcut("execution(public * cn.xej.boot_redis01.mapper..*(..))")
//    public void log(){
//
//    }

    private static String[] types = {"java.lang.Integer", "java.lang.Double",
            "java.lang.Float", "java.lang.Long", "java.lang.Short",
            "java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
            "java.lang.String", "int", "double", "long", "short", "byte",
            "boolean", "char", "float"};


    private static HashMap<String, Class> map = new HashMap<String, Class>() {
        {
            put("java.lang.Integer", int.class);
            put("java.lang.Double", double.class);
            put("java.lang.Float", float.class);
            put("java.lang.Long", long.class);
            put("java.lang.Short", short.class);
            put("java.lang.Boolean", boolean.class);
            put("java.lang.Char", char.class);
        }
    };


    @Pointcut("@annotation(cn.xej.boot_redis01.aop.ModelSumbit)")
    private void pc(){
    }

    @Pointcut("@annotation(cn.xej.boot_redis01.aop.FieldSumbit)")
    private void fld(){

    }


    @Before("ServiceAspect.pc()")
    public void doBefore(){
        System.out.println("进入切面方法执行前...");
        System.out.println(tbLog);
    }

    @After("ServiceAspect.pc()")
    public void doAfter(){
//        System.out.println("进入切面方法执行后...");
    }


    //环绕通知,获取实体类
    @Around("ServiceAspect.fld()")
    public Object around1(ProceedingJoinPoint pjp) throws Throwable{
        tbLog = new TbLog();
        StringBuilder sb = new StringBuilder();
        //获取所有的参数
        Object[] args = pjp.getArgs();
        for (int k = 0; k < args.length; k++) {
            Object arg = args[k];
            // 获取对象类型
            String typeName = arg.getClass().getTypeName();
            //2 通过反射获取实体类属性
            sb.append(getFieldsValue(arg));
        }

        System.out.println(sb.toString());

        String[] fieldsName = getFieldsName(pjp);
        for (int i = 0; i < fieldsName.length; i++) {
            System.out.println(fieldsName[i]);
        }

        Object proceed = pjp.proceed();
        return proceed;
    }


    //环绕通知,获取sql
//    @Around("ServiceAspect.pc()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("++++++++++环绕通知开始+++++++++++");

        //1.从redis中获取主数据库，若获取不到直接退出，否则判断当前数据源是会否为主，若不为主，则切换到主数据源
        //2.调用目标方法
        ServletRequestAttributes sra =  (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (!Objects.isNull(sra) && !Objects.isNull(sra.getRequest())){
            HttpServletRequest request = sra.getRequest();
            String url = request.getRequestURL().toString();
            String ip = request.getRemoteAddr();
            String method = pjp.getSignature().getName();
            tbLog.setUrl(url);
            tbLog.setIp(ip);
            tbLog.setMethod(method);
        }

        Object proceed = pjp.proceed();
        //3.获取SQL
        String sql = SqlUtils.getMybatisSql(pjp, sqlSessionFactory);
        tbLog.setSql(sql);
        tbLogMapper.insertLog(tbLog);
        System.out.println(sql);

        return proceed;
    }


    //返回方法的参数名
    private static String[] getFieldsName(JoinPoint joinPoint) throws ClassNotFoundException, NoSuchMethodException {
        String classType = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Class<?>[] classes = new Class[args.length];
        for (int k = 0; k < args.length; k++) {
            if (!args[k].getClass().isPrimitive()) {
                //获取的是封装类型而不是基础类型
                String result = args[k].getClass().getName();
                Class s = map.get(result);
                classes[k] = s == null ? args[k].getClass() : s;
            }
        }
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        //获取指定的方法，第二个参数可以不传，但是为了防止有重载的现象，还是需要传入参数的类型
        Method method = Class.forName(classType).getMethod(methodName, classes);
        String[] parameterNames = pnd.getParameterNames(method);
        return parameterNames;
    }

    //解析实体类，获取实体类中的属性
    public static String getFieldsValue(Object obj) {
        //通过反射获取所有的字段，getFileds()获取public的修饰的字段
        //getDeclaredFields获取private protected public修饰的字段
        Field[] fields = obj.getClass().getDeclaredFields();
        String typeName = obj.getClass().getTypeName();
        for (String t : types) {
            if (t.equals(typeName)) {
                return "";
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        for (Field f : fields) {
            //在反射时能访问私有变量
            f.setAccessible(true);
            try {
                for (String str : types) {
                    //这边会有问题，如果实体类里面继续包含实体类，这边就没法获取。
                    //其实，我们可以通递归的方式去处理实体类包含实体类的问题。
                    if (f.getType().getName().equals(str)) {
                        sb.append(f.getName() + " : " + f.get(obj) + ", ");
                    }
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        sb.append("}");
        return sb.toString();
    }

}
